package org.oep.jmingle;
import org.oep.widgets.MonologueWidget;
import org.oep.bluetooth.*;
import org.oep.widgets.ChoiceWidget;
import java.io.IOException;
import java.util.Vector;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.LocalDevice;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.TiledLayer;
import javax.microedition.lcdui.game.LayerManager;

public class Field extends GameCanvas implements Runnable, BluetoothClient {
	private Actor player;
	private BTHandler bt = new BTHandler();
	private MingleTracker tracker;
	private Thread gameThread;
	private Vector inhabitants = new Vector();
	private ChoiceWidget choiceMenu;
	private Actor mingleSelection;
	private TiledLayer background;
	private LayerManager viewfinder;
	private Camera camera = new Camera();
	private MonologueWidget monologueBox;
	private IDWidget idBox;
	private EventPipe eventHandler;
	
	private boolean searching = false;
	private boolean menuShowsActors = false;
	private boolean menuShowsActions = false;
	private boolean menuShowsMain = false;
	private boolean fireDown = false; // Prevents repeated FIRE presses
	private boolean menuCycle = false; // Prevents repeated DIRECTION presses on choice menu
	
	private int BOUNDARY_TOP = 0;
	private int BOUNDARY_BOTTOM = getHeight();
	private int BOUNDARY_LEFT = 0;
	private int BOUNDARY_RIGHT = getWidth();
	private int PLAYER_SPEED = 2;
	
	private int CHOICE_MINGLE, CHOICE_INFO,
		CHOICE_MENU, CHOICE_CANCEL, CHOICE_EXIT, CHOICE_SCAN, CHOICE_BROWSE;
	
	
	public Field(MingleTracker t) {
		super(true);
		tracker = t;
		construct();
	}
	
	public void run() {
		while(true) { 
			// checks if the game has reached 
			// some boundary states or special conditions
			verifyGameState(); 
		
			// gets input from user and 
			// updates instance variables 
			// that describe the games elements
		
			checkUserInput(); 
		
			// paints elements on screen to reflect
			// the current game state using the current 
			// graphics object
		
			updateGameScreen(getGraphics());
		
		
			// control the rate at which screen updates are done 
		
			try {
				Thread.sleep(50); 
			}
			catch(InterruptedException e) {
				System.err.println(e);
			}
			
		}
	}
	
	private void verifyGameState() {
		// Nothin...
	}
	
	private void checkUserInput() {
		int keyStates = this.getKeyStates();
		
		// Initially, we process things that are only handled by the FIRE key
		if((keyStates & FIRE_PRESSED) != 0 && !fireDown) {
			handleFire();
			fireDown = true;
		} else if((keyStates & FIRE_PRESSED) == 0)
			fireDown = false;
		
		
		// These couple of things rely on more than FIRE, so we pass the keyStates
		if(choiceMenu != null) {
			updateMenuState(keyStates);
		}
		else if(idBox.isActive()) {
			updateIDBoxState(keyStates);
		}
		// Do not update player position if a widget is active
		else if(!idBox.isActive() && !monologueBox.isActive()) {
			updatePlayerPosition(keyStates);
		}
		

	}
	
	private void updateIDBoxState(int keyStates) {
		
		if((keyStates & LEFT_PRESSED) != 0) {
			idBox.previous();
		}
		else if((keyStates & RIGHT_PRESSED) != 0) {
			idBox.next();
		}
	}
	
	private void updateGameScreen(Graphics g) {
		g.setFont(JMingle.DEFAULT_FONT);
		g.setColor(0xFFFFFF);
		g.fillRect(0, 0, getWidth(), getHeight());
		
		if(searching) {
			g.setColor(0);
			g.drawString("Searching...", getWidth() / 2, getHeight() / 2, Graphics.HCENTER | Graphics.BOTTOM);
			g.drawString("Press FIRE to cancel", getWidth() / 2, getHeight() / 2 + g.getFont().getHeight(), Graphics.HCENTER | Graphics.BOTTOM);
			flushGraphics();
			return;
		}
		
		
		int camX = Math.min(
				BOUNDARY_RIGHT - getWidth() / 2,
				Math.max(player.getX(), getWidth() / 2)
				);
		camera.setSubjectPosition(camX, player.getY());
		camera.interpolate();
		
		viewfinder.setViewWindow(camera.getX() - getWidth() / 2, 0, getWidth(), getHeight());
		viewfinder.paint(g, 0, 0);
		
		// We'll paint the player in a second
		boolean playerPainted = false;
		player.nextFrame();
		
		// Paint the inhabitants
		for(int i = 0; i < inhabitants.size(); i++) {
			Actor a = (Actor) inhabitants.elementAt(i);
			
			a.nextFrame();
			if(a.collidesWith(player)) {
				// To help layer correctly
				if(!playerPainted && player.getY() < a.getY()) paintActor(player);
				paintActor(a);
				
				// We actually will want to repaint if the player is in front
				if(player.getY() >= a.getY()) paintActor(player);
				
				// Don't paint the player anymore
				playerPainted = true;
			} else {
				paintActor(a);
			}
		}
		
		if(!playerPainted) paintActor(player);
		
		if(choiceMenu != null)
			choiceMenu.setPosition(player.getX(), player.getY() - player.getHeight() / 2);
		paintChoiceWidget(choiceMenu);
		
/*		g.setColor(0);
		g.drawString("P: (" + player.getX() + ", " + player.getY() + ")", 0, 0, Graphics.TOP | Graphics.LEFT);
		g.drawString("S: (" + camera.getSubjectX() + ", " + camera.getSubjectY() + ")", 0, g.getFont().getHeight(), Graphics.TOP | Graphics.LEFT);
		g.drawString("C: (" + camera.getSubjectX() + ", " + camera.getSubjectY() + ")", 0, 2*g.getFont().getHeight(), Graphics.TOP | Graphics.LEFT);
		g.drawString("D: " + getWidth() + "x" + getHeight() + " @ " + BOUNDARY_RIGHT, 0, 3*g.getFont().getHeight(), Graphics.TOP | Graphics.LEFT);*/
		
		monologueBox.tick();
		monologueBox.paint(g);
		
		idBox.paint(g);
		flushGraphics();
	}
	
	private void paintChoiceWidget(ChoiceWidget c) {
		if(c == null) return;
		Graphics g = getGraphics();
		c.setPosition(getWidth() / 2 + (c.getX() - camera.getX()), c.getY());
		c.paint(g);
	}
	
	private void paintActor(Actor a) {
		if(a == null) return;
		
		Graphics g = getGraphics();
		
		// We'd like to paint everything relative to the camera
		// Assume the camera's position is the center of the screen
		// If this is the case, we shouldn't paint the actor if its position
		// is > getWidth() / 2, since that is off-camera
		if(Math.abs(a.getX() - camera.getX()) > getWidth() / 2 + a.getWidth() / 2)
			return;
		
		a.paint(g, getWidth() / 2 + (a.getX() - camera.getX()), a.getY());
		
		if(a != player && a.collidesWith(player)) {
			g.setColor(0);
			String str = a.getName();
			g.setFont(JMingle.DEFAULT_FONT);
			g.drawString(str, getWidth() / 2 + (a.getX() - camera.getX()), a.getY() - a.getHeight(), Graphics.HCENTER | Graphics.BOTTOM);
		}
	}
	
	private void updatePlayerPosition(int keyStates) {
		int x = player.getX();
		int y = player.getY();
		boolean stateSet = false;
		if((keyStates & UP_PRESSED) != 0) {
			y = Math.max(BOUNDARY_TOP + player.getHeight(), y - PLAYER_SPEED);
			if(!stateSet) {
				stateSet = true;
				player.setState(Actor.WALK_UP);
			}
		}
		if((keyStates & DOWN_PRESSED) != 0) {
			y = Math.min(BOUNDARY_BOTTOM, y + PLAYER_SPEED);
			if(!stateSet) {
				stateSet = true;
				player.setState(Actor.WALK_DOWN);
			}
		}
		if((keyStates & LEFT_PRESSED) != 0) {
			x = Math.max(BOUNDARY_LEFT + player.getWidth() / 2, x - PLAYER_SPEED);
			if(!stateSet) {
				stateSet = true;
				player.setState(Actor.WALK_LEFT);
			}
		}
		if((keyStates & RIGHT_PRESSED) != 0) {
			x = Math.min(BOUNDARY_RIGHT - player.getWidth() / 2, x + PLAYER_SPEED);
			if(!stateSet) {
				stateSet = true;
				player.setState(Actor.WALK_RIGHT);
			}
		}

		
		if(keyStates == 0)
			player.setState(Actor.STAND_STILL);
		player.setPosition(x, y);
	}
	
	private void updateMenuState(int keyStates) {
		int DIRECTION_PRESSED = UP_PRESSED + DOWN_PRESSED + LEFT_PRESSED + RIGHT_PRESSED;
		
		if(player.getState() != Actor.STAND_STILL)
			player.setState(Actor.STAND_STILL);
		
		if(!menuCycle && (keyStates & RIGHT_PRESSED) != 0 || (keyStates & DOWN_PRESSED) != 0) {
			choiceMenu.nextChoice();
			menuCycle = true;
		}
		
		else if(!menuCycle && (keyStates & LEFT_PRESSED) != 0  || (keyStates & UP_PRESSED) != 0) {
			choiceMenu.previousChoice();
		}
		
		if((keyStates & DIRECTION_PRESSED) == 0) {
			menuCycle = false;
		}
		else
			menuCycle = true;
	}
	
	public void construct() {
		String hash, name;
		
		try {
			hash = LocalDevice.getLocalDevice().getBluetoothAddress();
			name = LocalDevice.getLocalDevice().getFriendlyName();
		}
		catch(BluetoothStateException e) {
			hash = "0";
			name = "---";
		}
		
		player = Utils.makeActor(hash,name);
		
		camera.setInterpolationFactor(0.2);
		camera.setPosition(player.getX(), player.getY());
		
		monologueBox = new MonologueWidget( getWidth() / 4, getHeight() / 4, getWidth() / 2, 3, 3);
		monologueBox.setFont(JMingle.DEFAULT_FONT);
		idBox = new IDWidget(0, 0, getWidth(), player.getHeight() + 4 * getGraphics().getFont().getHeight());
		
		toInitialState();
	}
	
	public void pause() {
		if(gameThread != null && gameThread.isAlive())
			gameThread.interrupt();
	}
	
	public void start() {
		if(gameThread == null || !gameThread.isAlive()) {
			gameThread = new Thread(this);
			gameThread.start();
		}
	}
	
	public void toInitialState() {
		// There should be no other minglets at the start
		inhabitants.removeAllElements();
		
		// Build the background so we have appropriate resolution
		buildBackground();
		
		// Center the player
		player.setPosition(getWidth() / 2, (BOUNDARY_BOTTOM + BOUNDARY_TOP) / 2);
		
		// Center the camera on the player
		camera.setPosition(player.getX(), player.getY());
		
		// Destruct some stuff
		choiceMenu = null;
		mingleSelection = null;
		
		idBox.setActive(false);
		monologueBox.setActive(false);
	}
	
	private void buildBackground() {
		buildBackground(inhabitants.size());
	}
	
	private void buildBackground(int population) {
		// Let there be ample space: 2 minglets per unit width
		int width = Math.max(getWidth(), 2 * population * player.getWidth());
		int height = getHeight();
		try {
			Image im = Image.createImage("/res/graphics/bg-tiles.png");
			
			int	xTiles = 1 + (int) Math.ceil( width / 16 );
			int	yTiles = 1 + (int) Math.ceil( height / 16 );
			BOUNDARY_TOP = (16 * (yTiles - 5));
			BOUNDARY_RIGHT = (xTiles * 16);
			viewfinder = new LayerManager();
			viewfinder.setViewWindow(0, 0, getWidth(), getHeight());
			background = new TiledLayer(xTiles, yTiles, im, 16, 16);
			viewfinder.append(background);
			
			for(int i = 0; i < yTiles; i++) {
				for(int j = 0; j < xTiles; j++) {
					// Grass
					if(i > yTiles - 4)
						background.setCell(j, i, 7);
					// Grass tips
					else if(i == yTiles - 4)
						background.setCell(j, i, 6);
					// Sky
					else
						background.setCell(j, i, 5);
				}
			}
			
			// Sun
			background.setCell(xTiles - 3, 1, 1);
			background.setCell(xTiles - 2, 1, 2);
			background.setCell(xTiles - 3, 2, 3);
			background.setCell(xTiles - 2, 2, 4);
		
		}
		catch(IOException e) {
			
		}
	}
	
	public void newArea() {
		bt.doNewSearch(this);
		System.out.println("SERCH");
		searching = true;
	}
	
	private void handleFire() {		
		// 1st priority: cancelling a search
		if(searching ) {
			bt.stopScan();
			searching = false;
			return;
		}
		
		// Next, monologueBox
		if(monologueBox.isActive()) {
			monologueBox.action();
			return;
		}
		
		// Next, the infowidget will absorb it
		if(idBox.isActive()) {
			idBox.setActive(false);
			return;
		}
		
		// Otherwise, it's the choicewidget		
		if(choiceMenu == null) {
			
			// In this case, the player has no previous actions. We handle this in two ways
			// 1. See who the player is touching and offer to mingle take an action with them
			// 2. If they aren't touching anyone, just present the main menu
			// This is all handled by buildMenuFromCollisions()
			choiceMenu = buildMenuFromCollisions();
			
		} else if(menuShowsMain) {
			
			// The user was looking at the main menu previously. Just handle
			// the action the player selected
			
			if(choiceMenu.getChoiceIndex() == CHOICE_SCAN) {
				this.newArea();
			}
			else if(choiceMenu.getChoiceIndex() == CHOICE_BROWSE) {
				handleBrowseBox();
			}
			else if(choiceMenu.getChoiceIndex() == CHOICE_EXIT) {
				if(eventHandler != null)
					eventHandler.eventReceived(JMingle.SHOW_MAIN);
			}
			
			choiceMenu = null;
			menuShowsMain = false;
			
		} else if(menuShowsActors) {
			
			// This case means that previously the player was selecting a Minglet to
			// mingle with. The player could have selected to see the main menu, however.
			
			if(choiceMenu.getChoiceIndex() == CHOICE_MENU) {
				choiceMenu = buildMainMenu();
			}
			else {
				mingleSelection = (Actor) choiceMenu.getChoiceObject();
				choiceMenu = buildMenuFromActions();
			}
			
		} else if(menuShowsActions) {
			
			// In this case, the player was choosing what action to take with
			// the previously selected Minglet. We just handle their selection
			
			if(choiceMenu.getChoiceIndex() == CHOICE_MINGLE && mingleSelection != null) {
				// We mingled with someone!
				handleMingle();
			}
			else if(choiceMenu.getChoiceIndex() == CHOICE_INFO && mingleSelection != null) {
				// Getting info about someone!
				handleIDBox();
			}
			
			mingleSelection = null;
			choiceMenu = null;
			menuShowsActions = false;
		}
		
		// A catch-all way to customize our choice menu
		if(choiceMenu != null) {
			choiceMenu.setDefaultFont(JMingle.DEFAULT_FONT);
			choiceMenu.setSelectedFont(JMingle.DEFAULT_FONT);
			choiceMenu.setDiameter( (player.getHeight() * 15) / 10  );
		}
	}
	
	private void handleIDBox() {
		idBox.clear();
		idBox.addSubject(mingleSelection);
		idBox.setActive(true);
	}
	
	private void handleBrowseBox() {
		idBox.clear();
		idBox.setTracker(tracker);
		idBox.setActive(true);
	}
	
	private void handleMingle() {
		monologueBox.setActive(true);
		monologueBox.clearBuffer();
		monologueBox.display("Mingled with " + mingleSelection.getName() + "!");
		try {
			monologueBox.display( "Last seen " + tracker.getLastSeenTime(mingleSelection) + " ago!" );
		}
		catch(Exception e) {
			
		}
		mingleSelection.setLastSeen( System.currentTimeMillis() );
		tracker.doMingle(mingleSelection);
	}
	
	private ChoiceWidget buildMenuFromCollisions() {
		menuShowsActors = true;
		menuShowsActions = false;
		ChoiceWidget c = new ChoiceWidget(ChoiceWidget.TYPE_CIRCULAR);
		
		int collisions = 0;
		for(int i = 0; i < inhabitants.size(); i++) {
			Actor a = (Actor) inhabitants.elementAt(i);
			if(a.collidesWith(player)) {
				collisions++;
				c.addChoice(a);
			}
		}
		
		CHOICE_MENU = c.addChoice("Menu");
		
		if(collisions >= 1) return c;
		else {
			menuShowsActors = false;
			menuShowsActions = false;
			return buildMainMenu();
		}
	}
	
	private ChoiceWidget buildMenuFromActions() {
		menuShowsActors = false;
		menuShowsMain = false;
		menuShowsActions = true;
		ChoiceWidget c = new ChoiceWidget(ChoiceWidget.TYPE_CIRCULAR);
		CHOICE_MINGLE = c.addChoice("Mingle");
		CHOICE_INFO = c.addChoice("Info");
		CHOICE_CANCEL = c.addChoice("Cancel");
		return c;
	}
	
	private ChoiceWidget buildMainMenu() {
		menuShowsActors = false;
		menuShowsMain = true;
		menuShowsActions = false;
		ChoiceWidget c = new ChoiceWidget(ChoiceWidget.TYPE_CIRCULAR);
		CHOICE_SCAN = c.addChoice("Scan");
		CHOICE_BROWSE = c.addChoice("Browse");
		CHOICE_CANCEL = c.addChoice("Cancel");
		CHOICE_EXIT = c.addChoice("Exit");
		return c;
	}


	public void receiveDeviceList(Vector v) {
		System.out.println("Now I'm done.");
		if(v == null){
			searching = false;
			return;
		}
		java.util.Random r = new java.util.Random();
		inhabitants.removeAllElements();
		
		buildBackground(v.size());
		
		for(int i = 0; i < v.size(); i++) {
			Device d = (Device) v.elementAt(i);
			
			Actor a;
			if(tracker.know( d.getID() )) {
				try {
					long time = tracker.getRawLastSeenTime(d.getID());
					System.out.println("Outside time: " + time);
					a = Utils.makeActor(d.getID(), d.getName(),  time);
					System.out.println("Got 'em!");
				}
				catch(Exception e) { // No such key
					System.err.println(e);
					a = Utils.makeActor(d.getID(), d.getName());
				}
			}
			else { 
				System.out.println("We did not know " + d.getID());
				a = Utils.makeActor(d.getID(), d.getName());
			}
			
			int x = BOUNDARY_LEFT + a.getWidth() / 2 + r.nextInt( (BOUNDARY_RIGHT - BOUNDARY_LEFT) - a.getWidth() );
			int y = BOUNDARY_TOP + a.getHeight() + r.nextInt( (BOUNDARY_BOTTOM - BOUNDARY_TOP) - a.getHeight());
			
			a.setPosition(x, y);
			if(inhabitants.size() == 0)
				inhabitants.addElement( a );
			else {
				// Keep list sorted by the Y position
				boolean added = false;
				for(int j = 0; j < inhabitants.size(); j++) {
					Actor b = (Actor) inhabitants.elementAt(j);
					if(a.getY() < b.getY() ) {
						inhabitants.insertElementAt(a, j);
						added = true;
						break;
					}
				}
				if(!added) inhabitants.addElement(a);
			}
			
			System.out.println("Seen " + a.getName());
		}
		
		player.setPosition(getWidth() / 2, player.getY());
		camera.setPosition(getWidth() / 2, getHeight() / 2);
		
		searching = false;
	}
	
	public void setTracker(MingleTracker t) {
		tracker = t;
	}
	
	public void setEventHandler(EventPipe e) {
		eventHandler = e;
	}
}
